/*
 * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 */

package com.sun.corba.se.impl.protocol;

import java.io.IOException;
import java.util.Iterator;
import java.rmi.RemoteException;

import javax.rmi.CORBA.Util;
import javax.rmi.CORBA.Tie;

import org.omg.CORBA.COMM_FAILURE;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.Request;
import org.omg.CORBA.NamedValue;
import org.omg.CORBA.NVList;
import org.omg.CORBA.Context;
import org.omg.CORBA.ContextList;
import org.omg.CORBA.ExceptionList;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.portable.RemarshalException;
import org.omg.CORBA_2_3.portable.InputStream;
import org.omg.CORBA_2_3.portable.OutputStream;
import org.omg.CORBA.portable.Delegate;
import org.omg.CORBA.portable.ServantObject;
import org.omg.CORBA.portable.ApplicationException;
import org.omg.CORBA.portable.UnknownException;
import org.omg.IOP.ExceptionDetailMessage;
import org.omg.IOP.TAG_CODE_SETS;

import com.sun.org.omg.SendingContext.CodeBase;

import com.sun.corba.se.pept.broker.Broker;
import com.sun.corba.se.pept.encoding.InputObject;
import com.sun.corba.se.pept.encoding.OutputObject;
import com.sun.corba.se.pept.protocol.ClientRequestDispatcher;
import com.sun.corba.se.pept.protocol.MessageMediator;
import com.sun.corba.se.pept.transport.Connection;
import com.sun.corba.se.pept.transport.OutboundConnectionCache;
import com.sun.corba.se.pept.transport.ContactInfo;

import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.ior.iiop.GIOPVersion;
import com.sun.corba.se.spi.ior.iiop.IIOPProfileTemplate;
import com.sun.corba.se.spi.ior.iiop.CodeSetsComponent;
import com.sun.corba.se.spi.oa.OAInvocationInfo;
import com.sun.corba.se.spi.oa.ObjectAdapterFactory;
import com.sun.corba.se.spi.orb.ORB;
import com.sun.corba.se.spi.orb.ORBVersion;
import com.sun.corba.se.spi.orb.ORBVersionFactory;
import com.sun.corba.se.spi.protocol.CorbaMessageMediator;
import com.sun.corba.se.spi.protocol.RequestDispatcherRegistry;
import com.sun.corba.se.spi.transport.CorbaContactInfo;
import com.sun.corba.se.spi.transport.CorbaContactInfoList;
import com.sun.corba.se.spi.transport.CorbaContactInfoListIterator;
import com.sun.corba.se.spi.transport.CorbaConnection;
import com.sun.corba.se.spi.logging.CORBALogDomains;

import com.sun.corba.se.spi.servicecontext.MaxStreamFormatVersionServiceContext;
import com.sun.corba.se.spi.servicecontext.ServiceContext;
import com.sun.corba.se.spi.servicecontext.ServiceContexts;
import com.sun.corba.se.spi.servicecontext.UEInfoServiceContext;
import com.sun.corba.se.spi.servicecontext.CodeSetServiceContext;
import com.sun.corba.se.spi.servicecontext.SendingContextServiceContext;
import com.sun.corba.se.spi.servicecontext.ORBVersionServiceContext;
import com.sun.corba.se.spi.servicecontext.MaxStreamFormatVersionServiceContext;
import com.sun.corba.se.spi.servicecontext.UnknownServiceContext;

import com.sun.corba.se.impl.encoding.CDRInputObject;
import com.sun.corba.se.impl.encoding.CodeSetComponentInfo;
import com.sun.corba.se.impl.encoding.CodeSetConversion;
import com.sun.corba.se.impl.encoding.EncapsInputStream;
import com.sun.corba.se.impl.encoding.MarshalOutputStream;
import com.sun.corba.se.impl.encoding.MarshalInputStream;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.sun.corba.se.impl.protocol.giopmsgheaders.ReplyMessage;
import com.sun.corba.se.impl.protocol.giopmsgheaders.KeyAddr;
import com.sun.corba.se.impl.protocol.giopmsgheaders.ProfileAddr;
import com.sun.corba.se.impl.protocol.giopmsgheaders.ReferenceAddr;
import com.sun.corba.se.impl.transport.CorbaContactInfoListIteratorImpl;
import com.sun.corba.se.impl.util.JDKBridge;

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import sun.corba.EncapsInputStreamFactory;

/**
 * ClientDelegate is the RMI client-side subcontract or representation
 * It implements RMI delegate as well as our internal ClientRequestDispatcher
 * interface.
 */
public class CorbaClientRequestDispatcherImpl
    implements
    ClientRequestDispatcher {

  private ConcurrentMap<ContactInfo, Object> locks =
      new ConcurrentHashMap<ContactInfo, Object>();

  public OutputObject beginRequest(Object self, String opName,
      boolean isOneWay, ContactInfo contactInfo) {
    ORB orb = null;
    try {
      CorbaContactInfo corbaContactInfo = (CorbaContactInfo) contactInfo;
      orb = (ORB) contactInfo.getBroker();

      if (orb.subcontractDebugFlag) {
        dprint(".beginRequest->: op/" + opName);
      }

      //
      // Portable Interceptor initialization.
      //

      orb.getPIHandler().initiateClientPIRequest(false);

      //
      // Connection.
      //

      CorbaConnection connection = null;

      // This locking is done so that multiple connections are not created
      // for the same endpoint
      // 7046238 - Synchronization on a single monitor for contactInfo parameters
      // with identical hashCode(), so we lock on same monitor for equal parameters
      // (which can refer to equal (in terms of equals()) but not the same objects)

      Object lock = locks.get(contactInfo);

      if (lock == null) {
        Object newLock = new Object();
        lock = locks.putIfAbsent(contactInfo, newLock);
        if (lock == null) {
          lock = newLock;
        }
      }

      synchronized (lock) {
        if (contactInfo.isConnectionBased()) {
          if (contactInfo.shouldCacheConnection()) {
            connection = (CorbaConnection)
                orb.getTransportManager()
                    .getOutboundConnectionCache(contactInfo).get(contactInfo);
          }
          if (connection != null) {
            if (orb.subcontractDebugFlag) {
              dprint(".beginRequest: op/" + opName
                  + ": Using cached connection: " + connection);
            }
          } else {
            try {
              connection = (CorbaConnection)
                  contactInfo.createConnection();
              if (orb.subcontractDebugFlag) {
                dprint(".beginRequest: op/" + opName
                    + ": Using created connection: " + connection);
              }
            } catch (RuntimeException e) {
              if (orb.subcontractDebugFlag) {
                dprint(".beginRequest: op/" + opName
                    + ": failed to create connection: " + e);
              }
              // REVISIT: this part similar to marshalingComplete below.
              boolean retry = getContactInfoListIterator(orb)
                  .reportException(contactInfo, e);
              // REVISIT:
              // this part similar to Remarshal in this method below
              if (retry) {
                if (getContactInfoListIterator(orb).hasNext()) {
                  contactInfo = (ContactInfo)
                      getContactInfoListIterator(orb).next();
                  unregisterWaiter(orb);
                  return beginRequest(self, opName,
                      isOneWay, contactInfo);
                } else {
                  throw e;
                }
              } else {
                throw e;
              }
            }
            if (connection.shouldRegisterReadEvent()) {
              // REVISIT: cast
              orb.getTransportManager().getSelector(0)
                  .registerForEvent(connection.getEventHandler());
              connection.setState("ESTABLISHED");
            }
            // Do not do connection reclaim here since the connections
            // are marked in use by registerWaiter() call and since this
            // call happens later do it after that.
            if (contactInfo.shouldCacheConnection()) {
              OutboundConnectionCache connectionCache =
                  orb.getTransportManager()
                      .getOutboundConnectionCache(contactInfo);
              connectionCache.stampTime(connection);
              connectionCache.put(contactInfo, connection);
              //              connectionCache.reclaim();
            }
          }
        }
      }

      CorbaMessageMediator messageMediator = (CorbaMessageMediator)
          contactInfo.createMessageMediator(
              orb, contactInfo, connection, opName, isOneWay);
      if (orb.subcontractDebugFlag) {
        dprint(".beginRequest: " + opAndId(messageMediator)
            + ": created message mediator: " + messageMediator);
      }

      // NOTE: Thread data so we can get the mediator in release reply
      // in order to remove the waiter in CorbaConnection.
      // We cannot depend on obtaining information in releaseReply
      // via its InputStream argument since, on certain errors
      // (e.g., client marshaling errors), the stream may be null.
      // Likewise for releaseReply "self".
      // NOTE: This must be done before initializing the message since
      // that may start sending fragments which may end up in "early"
      // replies or client marshaling exceptions.

      orb.getInvocationInfo().setMessageMediator(messageMediator);

      if (connection != null && connection.getCodeSetContext() == null) {
        performCodeSetNegotiation(messageMediator);
      }

      addServiceContexts(messageMediator);

      OutputObject outputObject =
          contactInfo.createOutputObject(messageMediator);
      if (orb.subcontractDebugFlag) {
        dprint(".beginRequest: " + opAndId(messageMediator)
            + ": created output object: " + outputObject);
      }

      // NOTE: Not necessary for oneways, but useful for debugging.
      // This must be done BEFORE message initialization since fragments
      // may be sent at that time.
      registerWaiter(messageMediator);

      // Do connection reclaim now
      synchronized (lock) {
        if (contactInfo.isConnectionBased()) {
          if (contactInfo.shouldCacheConnection()) {
            OutboundConnectionCache connectionCache =
                orb.getTransportManager()
                    .getOutboundConnectionCache(contactInfo);
            connectionCache.reclaim();
          }
        }
      }

      orb.getPIHandler().setClientPIInfo(messageMediator);
      try {
        // This MUST come before message is initialized so
        // service contexts may be added by PI because
        // initial fragments may be sent during message initialization.
        orb.getPIHandler().invokeClientPIStartingPoint();
      } catch (RemarshalException e) {
        if (orb.subcontractDebugFlag) {
          dprint(".beginRequest: " + opAndId(messageMediator)
              + ": Remarshal");
        }

        // NOTE: We get here because an interceptor raised ForwardRequest
        // and updated the IOR/Iterator.  Since we have a fresh iterator
        // hasNext should succeed.

        // REVISIT: We should feed ALL interceptor exceptions to
        // iterator.reportException so it can determine if it wants
        // to retry.  Right now, SystemExceptions will flow to the
        // client code.

        // REVISIT:
        // This assumes that interceptors update
        // ContactInfoList outside of subcontract.
        // Want to move that update to here.
        if (getContactInfoListIterator(orb).hasNext()) {
          contactInfo = (ContactInfo) getContactInfoListIterator(orb).next();
          if (orb.subcontractDebugFlag) {
            dprint("RemarshalException: hasNext true\ncontact info " + contactInfo);
          }

          // Fix for 6763340: Complete the first attempt before starting another.
          orb.getPIHandler().makeCompletedClientRequest(
              ReplyMessage.LOCATION_FORWARD, null);
          unregisterWaiter(orb);
          orb.getPIHandler().cleanupClientPIRequest();

          return beginRequest(self, opName, isOneWay, contactInfo);
        } else {
          if (orb.subcontractDebugFlag) {
            dprint("RemarshalException: hasNext false");
          }
          ORBUtilSystemException wrapper =
              ORBUtilSystemException.get(orb,
                  CORBALogDomains.RPC_PROTOCOL);
          throw wrapper.remarshalWithNowhereToGo();
        }
      }

      messageMediator.initializeMessage();
      if (orb.subcontractDebugFlag) {
        dprint(".beginRequest: " + opAndId(messageMediator)
            + ": initialized message");
      }

      return outputObject;

    } finally {
      if (orb.subcontractDebugFlag) {
        dprint(".beginRequest<-: op/" + opName);
      }
    }
  }

  public InputObject marshalingComplete(java.lang.Object self,
      OutputObject outputObject)
      throws
      ApplicationException,
      org.omg.CORBA.portable.RemarshalException {
    ORB orb = null;
    CorbaMessageMediator messageMediator = null;
    try {
      messageMediator = (CorbaMessageMediator)
          outputObject.getMessageMediator();

      orb = (ORB) messageMediator.getBroker();

      if (orb.subcontractDebugFlag) {
        dprint(".marshalingComplete->: " + opAndId(messageMediator));
      }

      InputObject inputObject =
          marshalingComplete1(orb, messageMediator);

      return processResponse(orb, messageMediator, inputObject);

    } finally {
      if (orb.subcontractDebugFlag) {
        dprint(".marshalingComplete<-: " + opAndId(messageMediator));
      }
    }
  }

  public InputObject marshalingComplete1(
      ORB orb, CorbaMessageMediator messageMediator)
      throws
      ApplicationException,
      org.omg.CORBA.portable.RemarshalException {
    try {
      messageMediator.finishSendingRequest();

      if (orb.subcontractDebugFlag) {
        dprint(".marshalingComplete: " + opAndId(messageMediator)
            + ": finished sending request");
      }

      return messageMediator.waitForResponse();

    } catch (RuntimeException e) {

      if (orb.subcontractDebugFlag) {
        dprint(".marshalingComplete: " + opAndId(messageMediator)
            + ": exception: " + e.toString());
      }

      boolean retry =
          getContactInfoListIterator(orb)
              .reportException(messageMediator.getContactInfo(), e);

      //Bug 6382377: must not lose exception in PI

      // Must run interceptor end point before retrying.
      Exception newException =
          orb.getPIHandler().invokeClientPIEndingPoint(
              ReplyMessage.SYSTEM_EXCEPTION, e);

      if (retry) {
        if (newException == e) {
          continueOrThrowSystemOrRemarshal(messageMediator,
              new RemarshalException());
        } else {
          continueOrThrowSystemOrRemarshal(messageMediator,
              newException);
        }
      } else {
        if (newException instanceof RuntimeException) {
          throw (RuntimeException) newException;
        } else if (newException instanceof RemarshalException) {
          throw (RemarshalException) newException;
        }

        // NOTE: Interceptor ending point will run in releaseReply.
        throw e;
      }
      return null; // for compiler
    }
  }

  protected InputObject processResponse(ORB orb,
      CorbaMessageMediator messageMediator,
      InputObject inputObject)
      throws
      ApplicationException,
      org.omg.CORBA.portable.RemarshalException {
    ORBUtilSystemException wrapper =
        ORBUtilSystemException.get(orb,
            CORBALogDomains.RPC_PROTOCOL);

    if (orb.subcontractDebugFlag) {
      dprint(".processResponse: " + opAndId(messageMediator)
          + ": response received");
    }

    // We know for sure now that we've sent a message.
    // So OK to not send initial again.
    if (messageMediator.getConnection() != null) {
      ((CorbaConnection) messageMediator.getConnection())
          .setPostInitialContexts();
    }

    // NOTE: not necessary to set MessageMediator for PI.
    // It already has it.

    // Process the response.

    Exception exception = null;

    if (messageMediator.isOneWay()) {
      getContactInfoListIterator(orb)
          .reportSuccess(messageMediator.getContactInfo());
      // Invoke Portable Interceptors with receive_other
      exception = orb.getPIHandler().invokeClientPIEndingPoint(
          ReplyMessage.NO_EXCEPTION, exception);
      continueOrThrowSystemOrRemarshal(messageMediator, exception);
      return null;
    }

    consumeServiceContexts(orb, messageMediator);

    // Now that we have the service contexts processed and the
    // correct ORBVersion set, we must finish initializing the stream.
    // REVISIT - need interface for this operation.
    ((CDRInputObject) inputObject).performORBVersionSpecificInit();

    if (messageMediator.isSystemExceptionReply()) {

      SystemException se = messageMediator.getSystemExceptionReply();

      if (orb.subcontractDebugFlag) {
        dprint(".processResponse: " + opAndId(messageMediator)
            + ": received system exception: " + se);
      }

      boolean doRemarshal =
          getContactInfoListIterator(orb)
              .reportException(messageMediator.getContactInfo(), se);

      if (doRemarshal) {

        // Invoke Portable Interceptors with receive_exception:
        exception = orb.getPIHandler().invokeClientPIEndingPoint(
            ReplyMessage.SYSTEM_EXCEPTION, se);

        // If PI did not change the exception, throw a
        // Remarshal.
        if (se == exception) {
          // exception = null is to maintain symmetry with
          // GenericPOAClientSC.
          exception = null;
          continueOrThrowSystemOrRemarshal(messageMediator,
              new RemarshalException());
          throw wrapper.statementNotReachable1();
        } else {
          //  Otherwise, throw the exception PI wants thrown.
          continueOrThrowSystemOrRemarshal(messageMediator,
              exception);
          throw wrapper.statementNotReachable2();
        }
      }

      // No retry, so see if was unknown.

      ServiceContexts contexts =
          messageMediator.getReplyServiceContexts();
      if (contexts != null) {
        UEInfoServiceContext usc =
            (UEInfoServiceContext)
                contexts.get(UEInfoServiceContext.SERVICE_CONTEXT_ID);

        if (usc != null) {
          Throwable unknown = usc.getUE();
          UnknownException ue = new UnknownException(unknown);

          // Invoke Portable Interceptors with receive_exception:
          exception = orb.getPIHandler().invokeClientPIEndingPoint(
              ReplyMessage.SYSTEM_EXCEPTION, ue);

          continueOrThrowSystemOrRemarshal(messageMediator, exception);
          throw wrapper.statementNotReachable3();
        }
      }

      // It was not a comm failure nor unknown.
      // This is the general case.

      // Invoke Portable Interceptors with receive_exception:
      exception = orb.getPIHandler().invokeClientPIEndingPoint(
          ReplyMessage.SYSTEM_EXCEPTION, se);

      continueOrThrowSystemOrRemarshal(messageMediator, exception);

      // Note: We should never need to execute this line, but
      // we should assert in case exception is null somehow.
      throw wrapper.statementNotReachable4();
    } else if (messageMediator.isUserExceptionReply()) {

      if (orb.subcontractDebugFlag) {
        dprint(".processResponse: " + opAndId(messageMediator)
            + ": received user exception");
      }

      getContactInfoListIterator(orb)
          .reportSuccess(messageMediator.getContactInfo());

      String exceptionRepoId = peekUserExceptionId(inputObject);
      Exception newException = null;

      if (messageMediator.isDIIRequest()) {
        exception = messageMediator.unmarshalDIIUserException(
            exceptionRepoId, (InputStream) inputObject);
        newException = orb.getPIHandler().invokeClientPIEndingPoint(
            ReplyMessage.USER_EXCEPTION, exception);
        messageMediator.setDIIException(newException);

      } else {
        ApplicationException appException =
            new ApplicationException(
                exceptionRepoId,
                (org.omg.CORBA.portable.InputStream) inputObject);
        exception = appException;
        newException = orb.getPIHandler().invokeClientPIEndingPoint(
            ReplyMessage.USER_EXCEPTION, appException);
      }

      if (newException != exception) {
        continueOrThrowSystemOrRemarshal(messageMediator, newException);
      }

      if (newException instanceof ApplicationException) {
        throw (ApplicationException) newException;
      }
      // For DII:
      // This return will be ignored - already unmarshaled above.
      return inputObject;

    } else if (messageMediator.isLocationForwardReply()) {

      if (orb.subcontractDebugFlag) {
        dprint(".processResponse: " + opAndId(messageMediator)
            + ": received location forward");
      }

      // NOTE: Expects iterator to update target IOR
      getContactInfoListIterator(orb).reportRedirect(
          (CorbaContactInfo) messageMediator.getContactInfo(),
          messageMediator.getForwardedIOR());

      // Invoke Portable Interceptors with receive_other:
      Exception newException = orb.getPIHandler().invokeClientPIEndingPoint(
          ReplyMessage.LOCATION_FORWARD, null);

      if (!(newException instanceof RemarshalException)) {
        exception = newException;
      }

      // If PI did not change exception, throw Remarshal, else
      // throw the exception PI wants thrown.
      // KMC: GenericPOAClientSC did not check exception != null
      if (exception != null) {
        continueOrThrowSystemOrRemarshal(messageMediator, exception);
      }
      continueOrThrowSystemOrRemarshal(messageMediator,
          new RemarshalException());
      throw wrapper.statementNotReachable5();

    } else if (messageMediator.isDifferentAddrDispositionRequestedReply()) {

      if (orb.subcontractDebugFlag) {
        dprint(".processResponse: " + opAndId(messageMediator)
            + ": received different addressing dispostion request");
      }

      // Set the desired target addressing disposition.
      getContactInfoListIterator(orb).reportAddrDispositionRetry(
          (CorbaContactInfo) messageMediator.getContactInfo(),
          messageMediator.getAddrDispositionReply());

      // Invoke Portable Interceptors with receive_other:
      Exception newException = orb.getPIHandler().invokeClientPIEndingPoint(
          ReplyMessage.NEEDS_ADDRESSING_MODE, null);

      // For consistency with corresponding code in GenericPOAClientSC:
      if (!(newException instanceof RemarshalException)) {
        exception = newException;
      }

      // If PI did not change exception, throw Remarshal, else
      // throw the exception PI wants thrown.
      // KMC: GenericPOAClientSC did not include exception != null check
      if (exception != null) {
        continueOrThrowSystemOrRemarshal(messageMediator, exception);
      }
      continueOrThrowSystemOrRemarshal(messageMediator,
          new RemarshalException());
      throw wrapper.statementNotReachable6();
    } else /* normal response */ {

      if (orb.subcontractDebugFlag) {
        dprint(".processResponse: " + opAndId(messageMediator)
            + ": received normal response");
      }

      getContactInfoListIterator(orb)
          .reportSuccess(messageMediator.getContactInfo());

      messageMediator.handleDIIReply((InputStream) inputObject);

      // Invoke Portable Interceptors with receive_reply:
      exception = orb.getPIHandler().invokeClientPIEndingPoint(
          ReplyMessage.NO_EXCEPTION, null);

      // Remember: not thrown if exception is null.
      continueOrThrowSystemOrRemarshal(messageMediator, exception);

      return inputObject;
    }
  }

  // Filters the given exception into a SystemException or a
  // RemarshalException and throws it.  Assumes the given exception is
  // of one of these two types.  This is a utility method for
  // the above invoke code which must do this numerous times.
  // If the exception is null, no exception is thrown.
  //
  // Note that this code is duplicated in GenericPOAClientSC.java
  protected void continueOrThrowSystemOrRemarshal(
      CorbaMessageMediator messageMediator, Exception exception)
      throws
      SystemException, RemarshalException {

    ORB orb = (ORB) messageMediator.getBroker();

    if (exception == null) {

      // do nothing.

    } else if (exception instanceof RemarshalException) {

      // REVISIT - unify with PI handling
      orb.getInvocationInfo().setIsRetryInvocation(true);

      // NOTE - We must unregister the waiter NOW for this request
      // since the retry will result in a new request id.  Therefore
      // the old request id would be lost and we would have a memory
      // leak in the responseWaitingRoom.
      unregisterWaiter(orb);

      if (orb.subcontractDebugFlag) {
        dprint(".continueOrThrowSystemOrRemarshal: "
            + opAndId(messageMediator)
            + ": throwing Remarshal");
      }

      throw (RemarshalException) exception;

    } else {

      if (orb.subcontractDebugFlag) {
        dprint(".continueOrThrowSystemOrRemarshal: "
            + opAndId(messageMediator)
            + ": throwing sex:"
            + exception);
      }

      throw (SystemException) exception;
    }
  }

  protected CorbaContactInfoListIterator getContactInfoListIterator(ORB orb) {
    return (CorbaContactInfoListIterator)
        ((CorbaInvocationInfo) orb.getInvocationInfo())
            .getContactInfoListIterator();
  }

  protected void registerWaiter(CorbaMessageMediator messageMediator) {
    if (messageMediator.getConnection() != null) {
      messageMediator.getConnection().registerWaiter(messageMediator);
    }
  }

  protected void unregisterWaiter(ORB orb) {
    MessageMediator messageMediator =
        orb.getInvocationInfo().getMessageMediator();
    if (messageMediator != null && messageMediator.getConnection() != null) {
      // REVISIT:
      // The messageMediator may be null if COMM_FAILURE before
      // it is created.
      messageMediator.getConnection().unregisterWaiter(messageMediator);
    }
  }

  protected void addServiceContexts(CorbaMessageMediator messageMediator) {
    ORB orb = (ORB) messageMediator.getBroker();
    CorbaConnection c = (CorbaConnection) messageMediator.getConnection();
    GIOPVersion giopVersion = messageMediator.getGIOPVersion();

    ServiceContexts contexts = messageMediator.getRequestServiceContexts();

    addCodeSetServiceContext(c, contexts, giopVersion);

    // Add the RMI-IIOP max stream format version
    // service context to every request.  Once we have GIOP 1.3,
    // we could skip it since we now support version 2, but
    // probably safer to always send it.
    contexts.put(MaxStreamFormatVersionServiceContext.singleton);

    // ORBVersion servicecontext needs to be sent
    ORBVersionServiceContext ovsc = new ORBVersionServiceContext(
        ORBVersionFactory.getORBVersion());
    contexts.put(ovsc);

    // NOTE : We only want to send the runtime context the first time
    if ((c != null) && !c.isPostInitialContexts()) {
      // Do not do c.setPostInitialContexts() here.
      // If a client interceptor send_request does a ForwardRequest
      // which ends up using the same connection then the service
      // context would not be sent.
      SendingContextServiceContext scsc =
          new SendingContextServiceContext(orb.getFVDCodeBaseIOR()); //d11638
      contexts.put(scsc);
    }
  }

  protected void consumeServiceContexts(ORB orb,
      CorbaMessageMediator messageMediator) {
    ServiceContexts ctxts = messageMediator.getReplyServiceContexts();
    ServiceContext sc;
    ORBUtilSystemException wrapper = ORBUtilSystemException.get(orb,
        CORBALogDomains.RPC_PROTOCOL);

    if (ctxts == null) {
      return; // no service context available, return gracefully.
    }

    sc = ctxts.get(SendingContextServiceContext.SERVICE_CONTEXT_ID);

    if (sc != null) {
      SendingContextServiceContext scsc =
          (SendingContextServiceContext) sc;
      IOR ior = scsc.getIOR();

      try {
        // set the codebase returned by the server
        if (messageMediator.getConnection() != null) {
          ((CorbaConnection) messageMediator.getConnection()).setCodeBaseIOR(ior);
        }
      } catch (ThreadDeath td) {
        throw td;
      } catch (Throwable t) {
        throw wrapper.badStringifiedIor(t);
      }
    }

    // see if the version subcontract is present, if yes, then set
    // the ORBversion
    sc = ctxts.get(ORBVersionServiceContext.SERVICE_CONTEXT_ID);

    if (sc != null) {
      ORBVersionServiceContext ovsc =
          (ORBVersionServiceContext) sc;

      ORBVersion version = ovsc.getVersion();
      orb.setORBVersion(version);
    }

    getExceptionDetailMessage(messageMediator, wrapper);
  }

  protected void getExceptionDetailMessage(
      CorbaMessageMediator messageMediator,
      ORBUtilSystemException wrapper) {
    ServiceContext sc = messageMediator.getReplyServiceContexts()
        .get(ExceptionDetailMessage.value);
    if (sc == null) {
      return;
    }

    if (!(sc instanceof UnknownServiceContext)) {
      throw wrapper.badExceptionDetailMessageServiceContextType();
    }
    byte[] data = ((UnknownServiceContext) sc).getData();
    EncapsInputStream in =
        EncapsInputStreamFactory.newEncapsInputStream((ORB) messageMediator.getBroker(),
            data, data.length);
    in.consumeEndian();

    String msg =
        "----------BEGIN server-side stack trace----------\n"
            + in.read_wstring() + "\n"
            + "----------END server-side stack trace----------";

    messageMediator.setReplyExceptionDetailMessage(msg);
  }

  public void endRequest(Broker broker, Object self, InputObject inputObject) {
    ORB orb = (ORB) broker;

    try {
      if (orb.subcontractDebugFlag) {
        dprint(".endRequest->");
      }

      // Note: the inputObject may be null if an error occurs
      //       in request or before _invoke returns.
      // Note: self may be null also (e.g., compiler generates null in stub).

      MessageMediator messageMediator =
          orb.getInvocationInfo().getMessageMediator();
      if (messageMediator != null) {
        if (messageMediator.getConnection() != null) {
          ((CorbaMessageMediator) messageMediator)
              .sendCancelRequestIfFinalFragmentNotSent();
        }

        // Release any outstanding NIO ByteBuffers to the ByteBufferPool

        InputObject inputObj = messageMediator.getInputObject();
        if (inputObj != null) {
          inputObj.close();
        }

        OutputObject outputObj = messageMediator.getOutputObject();
        if (outputObj != null) {
          outputObj.close();
        }

      }

      // XREVISIT NOTE - Assumes unregistering the waiter for
      // location forwards has already happened somewhere else.
      // The code below is only going to unregister the final successful
      // request.

      // NOTE: In the case of a recursive stack of endRequests in a
      // finally block (because of Remarshal) only the first call to
      // unregisterWaiter will remove the waiter.  The rest will be
      // noops.
      unregisterWaiter(orb);

      // Invoke Portable Interceptors cleanup.  This is done to handle
      // exceptions during stream marshaling.  More generally, exceptions
      // that occur in the ORB after send_request (which includes
      // after returning from _request) before _invoke:
      orb.getPIHandler().cleanupClientPIRequest();

      // REVISIT: Early replies?
    } catch (IOException ex) {
      // See CDRInput/OutputObject.close() for more info.
      // This won't result in a Corba error if an IOException happens.
      if (orb.subcontractDebugFlag) {
        dprint(".endRequest: ignoring IOException - " + ex.toString());
      }
    } finally {
      if (orb.subcontractDebugFlag) {
        dprint(".endRequest<-");
      }
    }
  }


  protected void performCodeSetNegotiation(CorbaMessageMediator messageMediator) {
    CorbaConnection conn =
        (CorbaConnection) messageMediator.getConnection();
    IOR ior =
        ((CorbaContactInfo) messageMediator.getContactInfo())
            .getEffectiveTargetIOR();
    GIOPVersion giopVersion = messageMediator.getGIOPVersion();

    // XXX This seems to be a broken double checked locking idiom: FIX IT!

    // conn.getCodeSetContext() is null when no other requests have
    // been made on this connection to trigger code set negotation.
    if (conn != null &&
        conn.getCodeSetContext() == null &&
        !giopVersion.equals(GIOPVersion.V1_0)) {

      synchronized (conn) {
        // Double checking.  Don't let any other
        // threads use this connection until the
        // code sets are straight.
        if (conn.getCodeSetContext() != null) {
          return;
        }

        // This only looks at the first code set component.  If
        // there can be multiple locations with multiple code sets,
        // this requires more work.
        IIOPProfileTemplate temp =
            (IIOPProfileTemplate) ior.getProfile().
                getTaggedProfileTemplate();
        Iterator iter = temp.iteratorById(TAG_CODE_SETS.value);
        if (!iter.hasNext()) {
          // Didn't have a code set component.  The default will
          // be to use ISO8859-1 for char data and throw an
          // exception if wchar data is used.
          return;
        }

        // Get the native and conversion code sets the
        // server specified in its IOR
        CodeSetComponentInfo serverCodeSets
            = ((CodeSetsComponent) iter.next()).getCodeSetComponentInfo();

        // Perform the negotiation between this ORB's code sets and
        // the ones from the IOR
        CodeSetComponentInfo.CodeSetContext result
            = CodeSetConversion.impl().negotiate(
            conn.getBroker().getORBData().getCodeSetComponentInfo(),
            serverCodeSets);

        conn.setCodeSetContext(result);
      }
    }
  }

  protected void addCodeSetServiceContext(CorbaConnection conn,
      ServiceContexts ctxs,
      GIOPVersion giopVersion) {

    // REVISIT.  OMG issue 3318 concerning sending the code set
    // service context more than once was deemed too much for the
    // RTF.  Here's our strategy for the moment:
    //
    // Send it on every request (necessary in cases of fragmentation
    // with multithreaded clients or when the first thing on a
    // connection is a LocateRequest).  Provide an ORB property
    // to disable multiple sends.
    //
    // Note that the connection is null in the local case and no
    // service context is included.  We use the ORB provided
    // encapsulation streams.
    //
    // Also, there will be no negotiation or service context
    // in GIOP 1.0.  ISO8859-1 is used for char/string, and
    // wchar/wstring are illegal.
    //
    if (giopVersion.equals(GIOPVersion.V1_0) || conn == null) {
      return;
    }

    CodeSetComponentInfo.CodeSetContext codeSetCtx = null;

    if (conn.getBroker().getORBData().alwaysSendCodeSetServiceContext() ||
        !conn.isPostInitialContexts()) {

      // Get the negotiated code sets (if any) out of the connection
      codeSetCtx = conn.getCodeSetContext();
    }

    // Either we shouldn't send the code set service context, or
    // for some reason, the connection doesn't have its code sets.
    // Perhaps the server didn't include them in the IOR.  Uses
    // ISO8859-1 for char and makes wchar/wstring illegal.
    if (codeSetCtx == null) {
      return;
    }

    CodeSetServiceContext cssc = new CodeSetServiceContext(codeSetCtx);
    ctxs.put(cssc);
  }

  protected String peekUserExceptionId(InputObject inputObject) {
    CDRInputObject cdrInputObject = (CDRInputObject) inputObject;
    // REVISIT - need interface for mark/reset
    cdrInputObject.mark(Integer.MAX_VALUE);
    String result = cdrInputObject.read_string();
    cdrInputObject.reset();
    return result;
  }

  protected void dprint(String msg) {
    ORBUtility.dprint("CorbaClientRequestDispatcherImpl", msg);
  }

  protected String opAndId(CorbaMessageMediator mediator) {
    return ORBUtility.operationNameAndRequestId(mediator);
  }
}

// End of file.
